/*
 * Copyright 2015-2016 Tumblr, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tumblr.backboard.example;

import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringSystem;
import com.tumblr.backboard.Actor;
import com.tumblr.backboard.MotionProperty;
import com.tumblr.backboard.Utils.MathUtils;
import com.tumblr.backboard.Utils.View;
import com.tumblr.backboard.imitator.Imitator;
import com.tumblr.backboard.imitator.MotionImitator;
import com.tumblr.backboard.imitator.ToggleImitator;
import com.tumblr.backboard.performer.MapPerformer;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.Component;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Point;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * An ring of views that bloom and then contract, with an selector that follows the finger.
 *
 * @author ericleong
 * @since 2021-05-07
 */
public class FlowerFragment extends BaseFraction {
    private static final int DIAMETER = 50;
    private static final int RING_DIAMETER = 350;
    private static final int NEAREST_DIAMETER = 300;
    private static final int CO_Y = 25;

    private static final float CO = 1.5f;

    private static final int OPEN = 1;
    private static final int CLOSED = 0;

    private DependentLayout mRootView;
    private Component mCircle;
    private Component[] mCircles;

    private double distSq(double x1, double y1, double x2, double y2) {
        return Math.pow(x2 - x1, VALUE_TWO) + Math.pow(y2 - y1, VALUE_TWO);
    }

    private Component nearest(float x, float y, Component[] views) {
        double minDistSq = Double.MAX_VALUE;
        Component minView = null;

        for (Component view : views) {
            int[] viewLocation = view.getLocationOnScreen();
            double distSq = distSq(x, y, viewLocation[0] + view.getWidth() / VALUE_TWO,
                    viewLocation[1] - NEAREST_DIAMETER + view.getHeight() / VALUE_TWO);
            if (distSq < Math.pow(CO * view.getWidth(), VALUE_TWO) && distSq < minDistSq) {
                minDistSq = distSq;
                minView = view;
            }
        }

        return minView;
    }

    /**
     * Snaps to the nearest circle.
     *
     * @author ericleong
     * @since 2021-05-07
     */
    private class SnapImitator extends MotionImitator {
        private SnapImitator(MotionProperty property) {
            super(property, 0, Imitator.TRACK_ABSOLUTE, Imitator.FOLLOW_SPRING);
        }

        @Override
        public void mime(float offset, float value, float delta, float dt, TouchEvent event) {
            // find the nearest view
            int[] circleLocation = mCircle.getLocationOnScreen();
            int statusBarHeight = getStatusBarHeight(getFractionAbility());

            Component nearest = nearest((int) event.getPointerPosition(event.getIndex()).getX() + circleLocation[0],
                    (int) event.getPointerPosition(event.getIndex()).getY() + circleLocation[1]
                            - (int) (NEAREST_DIAMETER * CO), mCircles);

            if (nearest != null) {
                int[] nearestLocation = nearest.getLocationOnScreen();

                // snap to it - remember to compensate for translation
                switch (mProperty) {
                    case X:
                        getSpring().setEndValue(nearestLocation[0] + nearest.getWidth() / VALUE_TWO
                                - mCircle.getLeft() - mCircle.getWidth() / VALUE_TWO);
                        break;
                    case Y:
                        getSpring().setEndValue(nearestLocation[1] - statusBarHeight + nearest.getHeight() / VALUE_TWO
                                - mCircle.getTop() - mCircle.getHeight() / VALUE_TWO - CO_Y);
                        break;
                    default:
                        break;
                }
            } else {
                // follow finger
                super.mime(offset, value, delta, dt, event);
            }
        }
    }

    @Override
    public int getUiContent() {
        return ResourceTable.Layout_fragment_flower;
    }

    @Override
    public void initComponent() {
        mRootView = (DependentLayout) mComponent;
        mCircles = new Component[VALUE_SIX];
        mCircle = mRootView.findComponentById(ResourceTable.Id_circle);
        final List<Integer> circles = new ArrayList<>();
        circles.add(ResourceTable.Graphic_circle_green);
        circles.add(ResourceTable.Graphic_circle_red);
        circles.add(ResourceTable.Graphic_circle_purple);
        circles.add(ResourceTable.Graphic_circle_orange);
        circles.add(ResourceTable.Graphic_circle_blue);
        circles.add(ResourceTable.Graphic_circle_gray);

        // layout params
        DependentLayout.LayoutConfig params = new DependentLayout.LayoutConfig(AttrHelper.vp2px(DIAMETER,
                getFractionAbility()), AttrHelper.vp2px(DIAMETER, getFractionAbility()));
        params.addRule(DependentLayout.LayoutConfig.CENTER_IN_PARENT);

        // create the circle views
        int colorIndex = 0;
        for (int index = 0; index < mCircles.length; index++) {
            mCircles[index] = new Component(getFractionAbility());

            mCircles[index].setLayoutConfig(params);
            ShapeElement shapeElement = new ShapeElement(getFractionAbility(), circles.get(colorIndex));

            mCircles[index].setBackground(shapeElement);

            colorIndex++;
            if (colorIndex >= circles.size()) {
                colorIndex = 0;
            }

            mRootView.addComponent(mCircles[index], 0);
        }

        circles.clear();

        /* Animations! */

        final SpringSystem springSystem = SpringSystem.create();

        // create spring
        final Spring spring = springSystem.createSpring();

        // add listeners along arc
        final double arc = 2 * Math.PI / mCircles.length;

        for (int index = 0; index < mCircles.length; index++) {
            Component view = mCircles[index];

            // map spring to an line segment from the center to the edge of the ring
            spring.addListener(new MapPerformer(view, View.TRANSLATION_X, 0, 1, 0,
                    (float) (RING_DIAMETER * Math.cos(index * arc))));

            spring.addListener(new MapPerformer(view, View.TRANSLATION_Y, 0, 1, 0,
                    (float) (RING_DIAMETER * Math.sin(index * arc))));

            spring.setEndValue(CLOSED);
        }

        final ToggleImitator imitator = new ToggleImitator(spring, CLOSED, OPEN);

        // move circle using finger, snap when near another circle, and bloom when touched
        new Actor.Builder(SpringSystem.create(), mCircle)
                .addMotion(new SnapImitator(MotionProperty.X), View.TRANSLATION_X)
                .addMotion(new SnapImitator(MotionProperty.Y), View.TRANSLATION_Y)
                .onTouchListener(imitator)
                .build();
    }

    /**
     * 获取状态栏高度
     *
     * @param context 上下文
     * @return 状态栏高度
     */
    private static int getStatusBarHeight(Context context) {
        int result = 0;
        Point point1 = new Point();
        Point point2 = new Point();
        Optional<Display> display = DisplayManager.getInstance().getDefaultDisplay(context);
        if (display.isPresent()) {
            display.get().getSize(point1);
            display.get().getRealSize(point2);
        }
        result = (int) (MathUtils.floatToSubtract(point2.getPointY(), point1.getPointY()));
        return result;
    }
}
